package uml.objects;

import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Point;
import java.awt.Stroke;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;

import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import uml.eventsystem.IObserver;
import uml.eventsystem.ISubject;
import uml.eventsystem.MouseHandler;
import uml.eventsystem.SubjectImpl;
import uml.eventsystem.events.UMLEvent;
import uml.eventsystem.events.UMLEventId;
import uml.util.UMLConstants;

/**
 * @author Evan Curtis
 *
 */
public class AnchorPoint extends JPanel implements UMLObject, MouseListener ,ISubject, IObserver
{	
	private Container m_context;
	boolean selected;
	private SubjectImpl m_subjectImplementation;
	public int x;
	public int y;
	private ArrayList<AnchorPoint> m_siblings;
	private Stroke m_stroke;
	private ArrowEnd m_end;
	private UMLEventId m_eventId = UMLEventId.NULL;
	private AnchorPoint m_parent;
	/**
	 * Creates a new object at <tt>Point</tt> location in the coordinate system of <tt>Container</tt> context
	 * @param location
	 * @param c
	 */
	public AnchorPoint(Point location, Container context, Stroke stroke, ArrowEnd end) 
	{
		x = location.x;
		y = location.y;
		m_context = context;
		this.setPreferredSize(UMLConstants.POINT_SIZE);
		int yOffset = UMLConstants.POINT_SIZE.height/2;
		int xOffset = UMLConstants.POINT_SIZE.width/2;
		this.setLocation(this.x - xOffset, this.y - yOffset);
		this.setBackground(UMLConstants.ANCHOR_POINT_BACKGROUND_COLOR);
		this.unselect();
		this.setOpaque(false);
		m_subjectImplementation = new SubjectImpl(this);
		m_siblings = new ArrayList<AnchorPoint>();
		m_end = end;
		m_stroke = stroke;
		this.addMouseListener(this);
		this.addMouseListener(MouseHandler.getMouseHandler());
		this.addMouseMotionListener(MouseHandler.getMouseHandler());
		m_eventId = UMLEventId.NULL;
		m_parent = null;
	}
	/**
	 * Creates a new object at point <x,y> in the coordinate system of <tt>Container</tt> context
	 * @param x
	 * @param y
	 * @param c
	 */
	public AnchorPoint(int x, int y, Container context, Stroke stroke, ArrowEnd end) 
	{
		this.x = x;
		this.y = y;
		m_context = context;
		this.setPreferredSize(UMLConstants.POINT_SIZE);
		int yOffset = UMLConstants.POINT_SIZE.height/2;
		int xOffset = UMLConstants.POINT_SIZE.width/2;
		this.setLocation(this.x - xOffset, this.y - yOffset);
		this.setBackground(Color.green);
		this.setVisible(true);
		m_subjectImplementation = new SubjectImpl(this);
		m_siblings = new ArrayList<AnchorPoint>();
		m_stroke = stroke;
		m_end = end;
		this.addMouseListener(this);
		this.addMouseListener(MouseHandler.getMouseHandler());
		this.addMouseMotionListener(MouseHandler.getMouseHandler());
		m_eventId = UMLEventId.NULL;
		m_parent = null;
	}
	/**
	 * returns the context of this AnchorPoint
	 * @return
	 */
	
	public Container getContext()
	{
		return m_context;
	}
	
	/**
	 *returns the Point this AnchorPoint is at 
	 * @return
	 */
	
	public Point getPoint()
	{
		return new Point(x,y);
	}
	
	/**
	 * sets this AnchorPoint at point p
	 * @param p
	 */
	
	public void setPoint(Point p)
	{
		this.x = p.x;
		this.y = p.y;
	}
	
	/**
	 * returns the stroke
	 * @return
	 */
	
	public Stroke getStroke()
	{
		return m_stroke;
	}
	
	/**
	 * sets the context of this AnchorPoint
	 * @param container
	 */
	
	public void setContext(Container container)
	{
		if(container == m_context)
			return;
		if(container instanceof ISubject)
		{
			unregisterForContext();
			registerForContext(container);
			this.setPoint(SwingUtilities.convertPoint(m_context, this.getPoint(), container));
			m_context = container;
			this.syncRepresentation();
		}
		else if (container != null)
			setContext(container.getParent());
	}
	
	/**
	 * set the context without converting the point dangerous
	 * @param container
	 */
	
	public void setContextWithoutConversion(Container container)
	{
		m_context = container;
	}
	public void unregisterForContext()
	{
		if(m_context instanceof ISubject)
		{
			ISubject subject = (ISubject)m_context;
			subject.removeObserver(this, UMLEventId.TRANSLATE);
			subject.removeObserver(this, UMLEventId.DELETE);
		}
	}
	
	/**
	 * registers this for the containers event
	 * @param container
	 */
	
	public void registerForContext(Container container)
	{
		if(container instanceof ISubject)
		{
			ISubject subject = (ISubject)container;
			subject.addObserver(this, UMLEventId.TRANSLATE);
			subject.addObserver(this, UMLEventId.DELETE);
		}
	}
	
	/**
	 * returns the siblings of this AnchorPoint
	 * @return
	 */
	
	public ArrayList<AnchorPoint> getSiblings()
	{
		return new ArrayList<AnchorPoint>(m_siblings);
	}
	
	/**
	 * adds a sibling to this
	 * @param sibling
	 */
	
	public void addSibling(AnchorPoint sibling)
	{
		m_siblings.add(sibling);
		sibling.setAnchorParent(this);
	}
	
	/**
	 * removes a sibling from this AcnhorPoint
	 * @param sibling
	 */
	
	public void removeSibling(AnchorPoint sibling)
	{
		m_siblings.remove(sibling);
		sibling.setAnchorParent(null);
	}
	
	/**
	 * Used to tell this object it is selected so it can change its state to let the user know it has been selected
	 */
	
	public void select()
	{
		this.setBorder(UMLConstants.SELECTED_ANCHOR_POINT_BORDER);
	}
	
	/**
	 * Used to tell this object it is unselected so it can change its state to let the user know it has been unselected
	 */
	
	public void unselect()
	{
		this.setBorder(UMLConstants.UNSELECTED_ANCHOR_POINT_BORDER);
	}
	
	@Override
	public Component getComponent() 
	{
		return this;
	}
	
	@Override
	public void translate(Point dp) 
	{
		//gets the width and height of the DiagramPanel
		int parentWidth = this.getParent().getWidth();
		int parentHeight = this.getParent().getHeight();
		
		//checks if new position is within the DiagramPanel
		if ((this.getX() + dp.x + 10 < parentWidth) && (this.getY() + dp.y + 10 < parentHeight) &&
			(this.getX() + dp.x > 0) && (this.getY() + dp.y > 0)) {
		
			this.translate(dp.x, dp.y);
			this.setLocation(this.getX() + dp.x, this.getY() + dp.y);
			this.notifyObservers(UMLEventId.REPAINT);
		}
	}
	
	/**
	 * translate this AnchorPoint
	 * @param dx
	 * @param dy
	 */
	
	public void translate(int dx, int dy)
	{
		x += dx;
		y += dy;
	}
	
	/**
	 * returns this AnchorPoint's ArrowEnd
	 * @return
	 */
	
	public ArrowEnd getArrowEnd()
	{
		return m_end;
	}
	
	/**
	 * returns the parent of this
	 * @return
	 */
	
	public AnchorPoint getAnchorParent()
	{
		return m_parent;
	}
	
	/**
	 * sets the parent of this to parent
	 * @param parent
	 */
	
	public void setAnchorParent(AnchorPoint parent)
	{
		m_parent = parent;
	}
	
	@Override
	public Integer getLayer() 
	{
		return UMLConstants.ARROW_LAYER;
	}
	
	@Override
	public void setLayer(Integer layer) 
	{
		
	}
	
	@Override
	public void addObserver(IObserver observer, UMLEventId eventId) 
	{
		m_subjectImplementation.addObserver(observer, eventId);		
	}
	
	@Override
	public void removeObserver(IObserver observer, UMLEventId eventId) 
	{
		m_subjectImplementation.removeObserver(observer, eventId);
	}
	
	@Override
	public void notifyObservers(UMLEventId eventId) 
	{
		m_subjectImplementation.notifyObservers(eventId);
	}
	
	@Override
	public void registerObserver(IObserver observer) 
	{
		addObserver(observer, UMLEventId.ANCHOR);
		addObserver(observer, UMLEventId.TRANSLATE);
		addObserver(observer, UMLEventId.REPAINT);
	}
	
	@Override
	public void handleNotification(UMLEvent event) 
	{
		switch(event.getEventId())
		{
		case TRANSLATE:
			syncRepresentation();
			break;
		case DELETE:
			anchor();
			break;
		default:
			break;
		}
	}
	
	/**
	 * Fixes the position of object representing this anchor point so it is correctly positioned
	 */
	
	private void syncRepresentation() 
	{
		int yOffset = UMLConstants.POINT_SIZE.height/2;
		int xOffset = UMLConstants.POINT_SIZE.width/2;
		Point p = new Point(this.x - xOffset, this.y - yOffset);
		p = SwingUtilities.convertPoint(m_context, p, this.getParent());
		this.setLocation(p);
		this.notifyObservers(UMLEventId.REPAINT);
	}
	
	/**
	 * sends out an AnchorEvent.  Temporalily hides this representation so that it doesn't interfere with the event.
	 */
	
	public void anchor()
	{
		this.setVisible(false);
		notifyObservers(UMLEventId.ANCHOR);
		this.setVisible(true);
	}
	@Override
	public void mouseClicked(MouseEvent arg0) {
		// TODO Auto-generated method stub
		
	}
	
	@Override
	public void mouseEntered(MouseEvent arg0) 
	{
		
	}
	
	@Override
	public void mouseExited(MouseEvent arg0) 
	{
		
	}
	
	@Override
	public void mousePressed(MouseEvent arg0) 
	{
		this.setContext(this.getParent());
	}
	
	@Override
	public void mouseReleased(MouseEvent arg0) 
	{
		anchor();
	}
	
	@Override
	public UMLEventId getEventId() 
	{
		return m_eventId;
	}
	
	@Override
	public void setEventId(UMLEventId event) 
	{
		m_eventId = event;
	}	
}